from matplotlib import pyplot as plt
from matplotlib.figure import Figure
from matplotlib.axes import Axes
from cartopy import crs as ccrs
import numpy as np
import xarray as xr
from matplotlib.contour import QuadContourSet
from matplotlib.gridspec import GridSpec
from . import cal
from typing import Union

def basemap(fig: Figure, mapRange: list, nrows: int = 1, ncols: int = 1,
            index: int = 1, proj: ccrs.Projection = None,
            central_longitude: float = 0,
            coastlines: bool | dict = {"color": "black", "linewidth": 0.5},
            gridlines: bool | dict = {"color": "k", "linestyle": (0, (5, 5)),
                                      "linewidth": 0.5},
            lon_ticks: list | None = None, lat_ticks: list | None = None,
            ):
    """
    以 subplot 画基本地图底图

    ----------------------------------

    :param fig: Figure
    :type fig: matplotlib.figure.Figure
    :param mapRange: 地图范围，[lon_min, lon_max, lat_min, lat_max]
    :type mapRange: list[float]
    :param nrows: int, optional, add_subplot 的参数
    :type nrows: int
    :param ncols: int, optional, add_subplot 的参数
    :type ncols: int
    :param index: int, optional, add_subplot 的参数
    :type index: int
    :param proj: ccrs.Projection, optional, 投影方式，默认为 PlateCarree
    :type proj: cartopy.crs.Projection
    :param central_longitude: float, optional, proj=None 时 PlateCarree 投影方式
        的中心经度，默认为 0。
    :type central_longitude: float
    :param coastlines: bool | dict, optional, 是否画海岸线，如果为 dict，则作为
        ax.coastlines 的参数传入
    :type coastlines: bool | dict
    :param lon_ticks: list | None, optional, 指定经度刻度，不设置则5度间隔
    :type lon_ticks: list[float] | None
    :param lat_ticks: list | None, optional, 指定纬度刻度，不设置则5度间隔
    :type lat_ticks: list[float] | None

    """
    if proj is None:
        proj = ccrs.PlateCarree(central_longitude=central_longitude)

    ax = fig.add_subplot(nrows, ncols, index, projection=proj)
    ax.set_extent(mapRange, crs=proj)

    if coastlines:
        ax.coastlines(**coastlines)

    if lon_ticks is None:
        lon_ticks = range(int(mapRange[0]), int(mapRange[1])+1, 5)
    if lat_ticks is None:
        lat_ticks = range(int(mapRange[2]), int(mapRange[3])+1, 5)

    if gridlines:
        gl = ax.gridlines(draw_labels=True, xlocs=lon_ticks, ylocs=lat_ticks,
                          **gridlines)
        gl.top_labels = False
        gl.right_labels = False

    return ax

def quiver(x: xr.DataArray | np.ndarray, y: xr.DataArray | np.ndarray,
           u: xr.DataArray | np.ndarray, v: xr.DataArray | np.ndarray, 
           ax: Axes=None, proj: ccrs.Projection = ccrs.PlateCarree(),
           scale: float = 1, units: str = 'xy', width: float = 0.1,
           lowRes: int = 4, zorder:int = 5, **kwargs):
    """画矢量场，能够自动降采样

    :param x: x坐标
    :type x: xr.DataArray | np.ndarray
    :param y: y
    :type y: xr.DataArray | np.ndarray
    :param u: 矢量场的 u 分量
    :type u: xr.DataArray | np.ndarray
    :param v: 矢量场的 v 分量
    :type v: xr.DataArray | np.ndarray
    :param ax: Axes, defaults to None
    :type ax: Axes, optional
    :param proj: 投影, defaults to ccrs.PlateCarree()
    :type proj: ccrs.Projection, optional
    :param scale: 缩放, defaults to 1
    :type scale: float, optional
    :param units: 矢量长度单位, defaults to 'xy'
    :type units: str, optional
    :param width: 宽度, defaults to 0.1
    :type width: float, optional
    :param lowRes: 降采样间隔, defaults to 4
    :type lowRes: int, optional
    :param zorder: zorder, defaults to 5
    :type zorder: int, optional
    :return: quiver对象
    :rtype: Quiver
    """
    
    if ax is None:
        ax = plt.gca()
    
    q = ax.quiver(x[::lowRes], y[::lowRes], 
                  u[::lowRes, ::lowRes], v[::lowRes, ::lowRes], 
                  transform=proj, scale=scale, units=units,
                  width=width, zorder=zorder, **kwargs)
    return q


def streamplot(x: xr.DataArray | np.ndarray, y: xr.DataArray | np.ndarray,
           u: xr.DataArray | np.ndarray, v: xr.DataArray | np.ndarray, 
           ax=None, proj: ccrs.Projection = ccrs.PlateCarree(),
           density=1, linewidth=0.7, color='k', zorder:int = 5, **kwargs):
    
    if ax is None:
        ax = plt.gca()
    
    q = ax.streamplot(x, y, u, v, 
                  transform=proj,density=density, linewidth=linewidth,
                    zorder=zorder, color=color, **kwargs)
    return q

def quiverkey(q, x: float = 0.92, y: float = 0.97, length: float = 1,
               unit:str = 'm/s', fontsize: int = 12):
    plt.quiverkey(q, x, y, length, "%s %s" % (length, unit), labelpos='E', 
                  coordinates='figure', fontproperties={'size': fontsize})

def colorbar(fig: Figure, axesRange: list, C, horizontal: bool = True, **kwargs):

    ax = fig.add_axes(axesRange)
    cb = plt.colorbar(C, cax=ax, 
                 orientation='horizontal' if horizontal else 'vertical',
                 **kwargs)
    return cb

def subplot_label(text: str, x: float = 0.02, y: float = 0.97, 
                  ax=None, bg: bool = True, bgPad=1, zorder=100, fontsize=12,
                  ha='left', va='top'):
    if ax is None:
        ax = plt.gca()

    if bg:
        ax.text(x, y, text, transform=ax.transAxes, bbox=dict(
            facecolor='white', edgecolor='none', pad=bgPad
            ), ha=ha, va=va, zorder=zorder, fontsize=fontsize)
    else:
        ax.text(x, y, text, transform=ax.transAxes, ha=ha, va=va, 
                zorder=zorder, fontsize=fontsize)

def part_of_cmap(cmap, start: float = 0, end: float = 1):
    """
    裁剪自带的 colormap 的一部分，生成新的 colormap

    """
    
    from matplotlib import cm
    from matplotlib.colors import LinearSegmentedColormap
    
    cmap = cm.get_cmap(cmap)
    colors = cmap(np.linspace(start, end, 256))
    new_cmap = LinearSegmentedColormap.from_list('part_of_%s' % cmap.name, colors)
    return new_cmap


def fill_land(color: str = '#888888',zorder: int = 0):
    """
    陆地填色
    """

    from cartopy.feature import LAND
    ax = plt.gca()
    ax.add_feature(LAND, facecolor=color, zorder=zorder)


def subplot_gl(ax, top=False, right=False, left=False, bottom=False,
               linestyle='--', linewidth=0.5, color='gray',
               xlocs=[-160, 120, 140, 160, 180],
               ylocs=[-20, -10, 0, 10, 20], drawTick=False, **kwargs):
    """
    邮票图画经纬度网格
    根据情况设置是否画上下左右的经纬度标签
    """
    gl = ax.gridlines(crs=ccrs.PlateCarree(0), draw_labels=True,
                    linewidth=linewidth, color=color, linestyle=linestyle,
                    xlocs=xlocs, ylocs=ylocs, **kwargs)
    gl.top_labels = top
    gl.right_labels = right
    gl.left_labels = left
    gl.bottom_labels = bottom
    if drawTick:
        ax.set_xticks(xlocs, crs=ccrs.PlateCarree())
        ax.set_yticks(ylocs, crs=ccrs.PlateCarree())
        ax.set_xticklabels([])
        ax.set_yticklabels([])

def set_hatch_color(contourf: QuadContourSet, color: str | list[str],
                     borderWidth=0):
    """
    设置用 contourf 画的 hatch 的颜色

    @param contourf: contourf 的返回对象。
    @param color: hatch 的颜色，可以是单个颜色，也可以是一个 list，
        对应每个 hatch 的颜色。
    @param borderWidth: hatch 外边界宽度，一般设置为0隐藏。
    """

    for i, collection in enumerate(contourf.collections):
        if isinstance(color, list):
            collection.set_edgecolor(color[i])
        else:
            collection.set_edgecolor(color)

    for collection in contourf.collections:
        collection.set_linewidth(borderWidth)


def grid_plot(row: int, col: int, ratio: float = 1.5, 
              figsize: tuple = None, figZoom: float = 1,
              sub_adj: tuple = (0.05, 0.95, 0.05, 0.95),
              wSpace: float = 0.05, hSpace: float = 0.05,
              returnGS: bool = False, subplot={}, **kwargs) -> tuple[Figure, list[Axes] | GridSpec]:
    """
    画阵列多子图框架，使用时用 axes 制图：
    ```
    row = 5
    col = 5
    fig, axes = grid_plot(row, col)

    for i in range(row):
        for j in range(col):
            ax = axes[i, j]
            ax.plot(yourData)

    ```

    Parameters
    ----------
    row : int
        行数
    col : int
        列数
    ratio : float
        子图的长宽比，用于自动确定figsize，figsize=None时有效
    figsize : tuple, optional
        图片大小，None时自动计算, by default None
    figZoom : float, optional
        图片放大倍数, by default 1，每一张子图为 figZoom(inch) 高。
    sub_adj : tuple, optional
        子图间距，(left, right, bottom, top), 同 subplots_adjust, 
        by default (0.05, 0.95, 0.05, 0.95)
    wSpace : float, optional
        子图水平间距, by default 0.05
    hSpace : float, optional
        子图垂直间距, by default 0.05
    subplot : dict, optional
        子图参数, by default 
    returnGS : bool, optional
        是否返回GridSpec对象，否则返回subplot数组。 by default False
    
    Returns
    -------
    fig : matplotlib.figure.Figure
        图片对象
    axes : list | matplotlib.gridspec.GridSpec
        子图数组或GridSpec对象

    """

    if figsize is None:
        # 绘图区高度
        fig_ratio = col*ratio/row
        figHeight = row * figZoom / (sub_adj[3] - sub_adj[2])
        figWidth = row * figZoom * fig_ratio / (sub_adj[1] - sub_adj[0])
        figsize = (figWidth, figHeight)

    fig = plt.figure(figsize=figsize)
    fig.subplots_adjust(left=sub_adj[0], right=sub_adj[1],
                        bottom=sub_adj[2], top=sub_adj[3])
    gs = GridSpec(row, col, figure=fig, wspace=wSpace, hspace=hSpace, **kwargs)

    if returnGS:
        return fig, gs

    axes = []
    for i in range(row):
        axes.append([])
        for j in range(col):
            axes[i].append(fig.add_subplot(gs[i, j], **subplot))
    
    return fig, axes

def emphasis_contour(ax, x, y, data, level: list[float] | np.ndarray,
                     emphasisLevel: float | list[float] | np.ndarray, 
                     colors='k', emphasisColor='r', linewidths=0.8,
                     emphasisLinewidth=1.3, contourArgs={}, clabelArgs={}) -> None:
    """
    画等值线，并强调其中某一些

    Parameters
    ----------
    ax: axes
        当前 axes
    x: array-liked
        contour的参数
    y: array-liked
        contour的参数
    data: array-liked
        contour的参数
    level: array-liked
        要画的等值线的值，包含要强调的值
    emphasisLevel: float | array-liked
        要强调的等值线的值
    colors: matplotlib colors
        非强调的等值线的颜色
    emphasisColor: matplotlib colors
        强调的等值线的颜色
    linewidths: float
        非强调的等值线宽度
    emphasisLinewidth: float
        强调的等值线宽度
    contourAugs: dict
        contour() 函数的其他参数，以字典形式传入
    clabel: dict
        clabel() 函数的其他参数，以字典形式传入

    """
    level = np.array(level)

    if not isinstance(emphasisLevel, list) and \
        not isinstance(emphasisLevel, np.ndarray):
        emphasisLevel = [emphasisLevel]

    emphasisLevel = np.array(emphasisLevel)
    level = np.delete(level, np.where(np.isin(level, emphasisLevel)))
    
    C1 = ax.contour(x, y, data, level, colors=colors, linewidths=linewidths, **contourArgs)

    C2 = ax.contour(x, y, data, emphasisLevel, colors=emphasisColor, 
               linewidths=emphasisLinewidth, **contourArgs)
    
    for C in [C1, C2]:
        clabel = ax.clabel(C, zorder=50, **clabelArgs)
        for c in clabel:
            c.set_bbox(dict(facecolor='white', edgecolor='None', pad=0.1))
    
def subplot_tick(xtick: list[float] | np.ndarray, ytick: list[float] | np.ndarray,
                 showYTickLabel: bool = True, showXTickLabel: bool = True, ax=None,
                 xAppend: str=None, yAppend: str=None, **kwargs):
    """
    多子图画坐标轴刻度，只在边缘画。例如

    ```
    for i in range(3):
        for j in range(4):
            ax = fig.add_subplots(3, 4, i*4+j)

            # 只在子图阵列的左边和下边显示 tick 的数值
            subplot_tick([0, 1, 2], [0, 1, 2],
                         showYTickLabel = j == 0,
                         showXTickLabel = i == 2)

    ```
    Parameters
    ----------
    xtick: array-liked
        x刻度值
    ytick: array-liked
        y刻度值
    showYTickLabel: bool
        是否显示y刻度的值
    showXTickLabel: bool
        是否显示x刻度的值
    ax: axes
        axes
    xAppend: str
        xtick的后缀，例如表示经度时可以加 "E"
    yAppend: str
        ytick的后缀

    """
    if ax is None:
        ax = plt.gca()

    if xAppend is not None:
        xtickLabel = [str(i)+xAppend for i in xtick]
    
    if yAppend is not None:
        ytickLabel = [str(i)+yAppend for i in ytick]

    ax.set_xticks(xtick)
    ax.set_xticklabels(xtickLabel if showXTickLabel else [], **kwargs)
    ax.set_yticks(ytick)
    ax.set_yticklabels(ytickLabel if showYTickLabel else [], **kwargs)


def grid_colorbar(fig: Figure, axesRange: list, number: int, horizontal=False, space=0.05, gs=False):
    """
    使用 grid_plot 时，出现每一行或每一列需要一个colorbar时使用。
    生成多个axes给colorbar使用。

    Parameters
    ----------
    fig: Figure

    axesRange: array-liked
        colorbar列表范围，分别为[左下角x坐标，左下角y坐标，宽度，高度]
    number: int
        colorbar数量
    horizontal: bool, default: False
        colorbar横向还是竖向分布
    space: float
        间隔

    Return
    ------
    axes: list[axes]

    """

    from matplotlib.gridspec import GridSpec

    if horizontal:
        gs = GridSpec(1, number, fig, left=axesRange[0], bottom=axesRange[1],
                      right=axesRange[0]+axesRange[2], 
                      top=axesRange[1]+axesRange[3], wspace=space)
    else:
        gs = GridSpec(number, 1, fig, left=axesRange[0], bottom=axesRange[1],
                      right=axesRange[0]+axesRange[2], 
                      top=axesRange[1]+axesRange[3], hspace=space)
    if gs:
        return gs
    axes = []
    for i in range(number):
        if horizontal:
            axes.append(fig.add_subplot(gs[0, i]))
        else:
            axes.append(fig.add_subplot(gs[i, 0]))
    
    return axes


def test_hatch(x, y, p, p_limit, hatch='.', color='k', 
               ax=None, transform=ccrs.PlateCarree(), **kwargs):
    """
    画检验的hatch
    """
    if ax is None:
        ax = plt.gca()
    
    h = ax.contourf(x, y, p, levels=[0, p_limit], 
                    colors='none', hatches=[hatch], transform=transform, **kwargs)

    return h


def axisColor(ax, left=None, right=None, which='left'):

    if left is not None:
        ax.spines['left'].set_color(left)
    if right is not None:
        ax.spines['right'].set_color(right)

    if which == 'left':
        ax.tick_params(axis='y', colors=left)
        ax.yaxis.label.set_color(left)
    elif which == 'right':
        ax.tick_params(axis='y', colors=right)
        ax.yaxis.label.set_color(right)
    

def draw_box(area: list[float], ax=None, transform=ccrs.PlateCarree(), div=1, **kwargs):
    """
    画框

    Parameters
    ----------
    area: list[float]
        方框范围[左右下上]
    ax: axes | None
        axes
    transform: ccrs | None
        投影
    div: int
        方框边的分段数，经纬度弯曲的投影下分度越高越准确
    **kwargs: dict
        传给ax.plot的参数

    Return
    ------
    P: list[matplotlib.lines.Line2D]

    """
    if ax is None:
        ax = plt.gca()

    if div < 1:
        div = 1

    x = np.linspace(area[0], area[1], div+1)
    y = np.linspace(area[2], area[3], div+1)
    P = ax.plot(
        np.concatenate([x, np.ones(div+1)*area[1], x[::-1], np.ones(div+1)*area[0]]),
        np.concatenate([np.ones(div+1)*area[2], y, np.ones(div+1)*area[3], y[::-1]]),
        transform=transform, **kwargs
    )
    return P
def draw_x_marker(lon, lat, r, linewidth=1, ax=None, 
                  transform=ccrs.PlateCarree(), div=1, **kwargs):
    """
    画x标记

    lon: float
        经度
    lat: float
        纬度
    r: float
        半径
    ax: axes | None
        axes
    linewidth: float
        线宽
    transform: ccrs | None
        投影
    div: int
        方框边的分段数，经纬度弯曲的投影下分度越高越准确
    **kwargs: dict
        传给ax.plot的参数

    Return
    ------
    P: list[matplotlib.lines.Line2D]

    """
    if ax is None:
        ax = plt.gca()

    if div < 1:
        div = 1

    x = np.linspace(lon-r, lon+r, div+1)
    y = np.linspace(lat-r, lat+r, div+1)
    ax.plot( x, y, transform=transform, linewidth=linewidth, **kwargs)
    ax.plot( x[::-1], y, transform=transform, linewidth=linewidth, **kwargs)


def get_marker(name: str):
    from matplotlib.path import Path
    marker = {
        "x": Path([(-1, -1), (1, 1), (1, -1), (-1, 1)] , np.array([1, 2, 1, 2], dtype=np.uint8)),
        "+" : Path([(-2.828, 0), (2.828, 0), (0, 2.828), (0, -2.828)] , np.array([1, 2, 1, 2], dtype=np.uint8)),
    }
    return marker[name]


def lonlat_gl_tick(ax: Axes, majorInterval: int=20, 
                   minorInterval: Union[float, bool]=10,
                   lonTicks: list[Union[int, float]]=None,
                   latTicks: list[Union[int, float]]=None,
                   drawGL: bool=True, drawTick: bool=True, 
                   drawLonLabel: bool=True, drawLatLabel: bool=True, glArgs={},
                   ticklabelsArgs={}) -> None:
    """画经纬度的网格和突出来的刻度和标签

    :param ax: Axes
    :type ax: Axes
    :param majorInterval: 主刻度间隔，设置lonTicks时无效, defaults to 20
    :type majorInterval: int, optional
    :param minorInterval: 副刻度间隔，false关闭副刻度，设置lonTicks/latTicks时无效, defaults to 10
    :type minorInterval: Union[float  |  False], optional
    :param lonTicks: 手动指定经度刻度, defaults to None
    :type lonTicks: list[Union[int, float]], optional
    :param latTicks: 手动指定纬度刻度, defaults to None
    :type latTicks: list[Union[int, float]], optional
    :param drawGL: 是否画网格线, defaults to True
    :type drawGL: bool, optional
    :param drawTick: 是否画突出来的刻度，如果为False，则使用gridlines()画标签，如果为True则使用axes原生ticks设置刻度, defaults to True
    :type drawTick: bool, optional
    :param drawLonLabel: 控制是否画经度标签, defaults to True
    :type drawLonLabel: bool, optional
    :param drawLatLabel: 控制是否画纬度表桥, defaults to True
    :type drawLatLabel: bool, optional
    :param glArgs: gridlinds的参数，有默认配置，如颜色、虚线等，可传入覆盖, defaults to {}
    :type glArgs: dict, optional
    :param ticklabelsArgs: Axes原生set_xticklabels()的参数, defaults to {}
    :type ticklabelsArgs: dict, optional
    """
    from cartopy.mpl.ticker import LongitudeFormatter, LatitudeFormatter
    from matplotlib.ticker import MultipleLocator

    glDefaultConfig = {
        "linestyle":'--', "linewidth":0.5, "color":'gray',
    }

    for k, v in glDefaultConfig.items():
        if k not in glArgs:
            glArgs[k] = v

    # 如果不指定刻度，则用主刻度间隔生成
    if lonTicks is None or latTicks is None:
        ax.xaxis.set_major_locator(MultipleLocator(majorInterval))
        ax.yaxis.set_major_locator(MultipleLocator(majorInterval))
        
        # 获取刻度
        lonTicks = ax.get_xticks()
        latTicks = ax.get_yticks()
    elif drawTick:
        ax.xaxis.set_major_formatter(LongitudeFormatter())
        ax.yaxis.set_major_formatter(LatitudeFormatter())
        ax.set_xticks(lonTicks, crs=ccrs.PlateCarree())
        ax.set_yticks(latTicks, crs=ccrs.PlateCarree())
        if not drawLonLabel:
            ax.set_xticklabels([])
        if not drawLatLabel:
            ax.set_yticklabels([])

    if minorInterval:
        ax.xaxis.set_minor_locator(MultipleLocator(minorInterval))
        ax.yaxis.set_minor_locator(MultipleLocator(minorInterval))

    if drawGL:
        gl = ax.gridlines(crs=ccrs.PlateCarree(0), draw_labels=not drawTick,
                        xlocs=lonTicks, ylocs=latTicks, **glArgs)
        gl.top_labels = False
        gl.right_labels = False
        if drawTick:
            gl.left_labels = False
            gl.bottom_labels = False
        else:
            gl.left_labels = drawLatLabel
            gl.bottom_labels = drawLonLabel

    
def useFont(fontName: str="Helvetica"):
    """使用指定字体

    :param fontName: 字体名, defaults to "Helvetica"
    :type fontName: str, optional
    """             
    from matplotlib import font_manager

    fontSets = {
        "Helvetica": "Helvetica.ttc",
        "Cascadia Mono": "CascadiaMono.ttf",
    }

    folderPath = '/Users/jjli/code/fonts/' + fontSets[fontName]
    font_manager.fontManager.addfont(folderPath)
    plt.rcParams['font.family'] = fontName
    plt.rcParams['mathtext.default'] = 'regular'


def labelLetter(n: int, prefix="(", suffix=")") -> str:
    """给子图标号，传入数字，返回字母

    :param n: 数字，0-25
    :type n: int
    :param prefix: 前缀, defaults to "("
    :type prefix: str, optional
    :param suffix: 后缀, defaults to ")"
    :type suffix: str, optional
    :raises ValueError: 数字应该小于26
    :return: 字符串，如"(a)"
    :rtype: str
    """
    if n > 25:
        raise ValueError("n should be less than 26")
    return prefix + 'abcdefghijklmnopqrstuvwxyz'[n] + suffix


def grid_plot_std(row: int, col: int, ratio: float = 1.5, 
              figWidth: float=6,
              sub_adj: tuple = (0.05, 0.95, 0.05, 0.95),
              wSpace: float = 0.05, hSpace: float = 0.05,
              returnGS: bool = False, subplot={}, **kwargs) -> tuple[Figure, list[Axes] | GridSpec]:
    """
    画阵列多子图框架，使用时用 axes 制图：
    ```
    :param row: 行数
    :type row: int
    :param col: 列数
    :type col: int
    :param ratio: 子图的长宽比，用于自动确定figsize，figsize=None时有效
    :type ratio: float
    :param figsize: 图片大小，None时自动计算
    :type figsize: tuple, optional
    :param figZoom: 图片放大倍数
    :type figZoom: float, optional
    :param sub_adj: 子图间距，(left, right, bottom, top), 同 subplots_adjust
    :type sub_adj: tuple, optional
    :param wSpace: 子图水平间距
    :type wSpace: float, optional
    :param hSpace: 子图垂直间距
    :type hSpace: float, optional
    :param subplot: 子图参数
    :type subplot: dict, optional
    :param returnGS: 是否返回GridSpec对象，否则返回subplot数组
    :type returnGS: bool, optional
    
    :return: fig : matplotlib.figure.Figure
        图片对象
    :rtype: axes : list | matplotlib.gridspec.GridSpec
        子图数组或GridSpec对象

    """

    # 绘图区高度
    fig_ratio = col*ratio/row
    figHeight = figWidth / fig_ratio / (sub_adj[3] - sub_adj[2])
    figsize = (figWidth, figHeight)

    fig = plt.figure(figsize=figsize)
    fig.subplots_adjust(left=sub_adj[0], right=sub_adj[1],
                        bottom=sub_adj[2], top=sub_adj[3])
    gs = GridSpec(row, col, figure=fig, wspace=wSpace, hspace=hSpace, **kwargs)

    if returnGS:
        return fig, gs

    axes = []
    for i in range(row):
        axes.append([])
        for j in range(col):
            axes[i].append(fig.add_subplot(gs[i, j], **subplot))
    
    return fig, axes


def std_figure(height: float, FULLWIDTH: float=6.2677, DPI: int=300) -> Figure:
    """横版全幅图片

    :param height: 高度，英寸
    :type height: float
    :param FULLWIDTH: 图片的宽度，单位英寸, 默认为6.2677，是A4减去两边的边距
    :type FULLWIDTH: float, optional
    :param DPI: 分辨率, 默认为300
    :type DPI: int, optional
    :return: figure对象
    :rtype: Figure
    """
    fig = plt.figure(figsize=(FULLWIDTH, height), dpi=DPI)
    return fig


def std_figure_half(height: float, FULLWIDTH: float=6.2677, MID_GAP: float=0.5, 
                    DPI: int=300) -> Figure:
    """双栏单边图片，宽度为全幅的一般减去中间的间隔

    :param height: 高度，英寸
    :type height: float
    :param FULLWIDTH: 全幅的宽度，单位英寸, 默认为6.2677，是A4减去两边的边距
    :type FULLWIDTH: float, optional
    :param MID_GAP: 中间的间隔，单位英寸, 默认为0.5
    :type MID_GAP: float, optional
    :param DPI: 分辨率, 默认为300
    :type DPI: int, optional
    :return: figure对象
    :rtype: Figure
    """
    fig = plt.figure(figsize=((FULLWIDTH-MID_GAP)/2, height), dpi=DPI)
    return fig


def std_rcParams():
    """设置标准制图对应的参数，文字、线宽都要比正常的小
    """
    # 文字大小，小四: 4.23mm，五号: 3.7mm，普通文字：2mm 或者 1.6mm
    FONTSIZE = 1.6 / 25.4 * 100

    plt.rcParams['font.size'] = FONTSIZE

    # 边框线宽
    plt.rcParams['axes.linewidth'] = 0.6
    # 刻度线宽
    plt.rcParams['xtick.major.width'] = 0.6
    plt.rcParams['ytick.major.width'] = 0.6
    plt.rcParams['xtick.minor.width'] = 0.4
    plt.rcParams['ytick.minor.width'] = 0.4
    # 刻度线长度
    plt.rcParams['xtick.major.size'] = 2.5
    plt.rcParams['ytick.major.size'] = 2.5
    plt.rcParams['xtick.minor.size'] = 1.5
    plt.rcParams['ytick.minor.size'] = 1.5
    # 刻度线标签间隔
    plt.rcParams['xtick.major.pad'] = 2
    plt.rcParams['ytick.major.pad'] = 2
    

def colorDarken(color:str, factor=0.5, s=1.2) -> str:
    """十六进制颜色加深，返回十六进制

    :param color: 颜色
    :type color: str
    :param factor: 加深因子, defaults to 0.5, 0为全黑，1为原色
    :type factor: float, optional
    :param s: 饱和度, defaults to 1.2
    :type s: float, optional
    :return: 加深后的颜色
    :rtype: str
    """
    from matplotlib.colors import rgb_to_hsv, hsv_to_rgb
    rgb = np.array([int(color[i:i+2], 16) for i in (1, 3, 5)])
    # 转 hsv
    hsv = rgb_to_hsv(rgb/255)
    hsv[2] *= factor
    hsv[1] *= s
    rgb = hsv_to_rgb(hsv)
    return f"#{''.join([f'{int(c*255):02x}' for c in rgb])}"